package club.jdiy.utils;

import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.IntStream;

/**
 * JDiy字符串处理工具类.
 *
 * @author ziquee(子秋) QQ号:39886616 主页: http://www.jdiy.club
 * @version 20200517
 */
@SuppressWarnings("ALL")
@Slf4j
public final class StringUtils extends org.apache.commons.lang3.StringUtils{
    private StringUtils() {
    }

    /**
     * 将字符串首字母转换成大写。
     *
     * @param str 要转换的字符串，如： name
     * @return 转换后的结果，如：Name
     */
    public static String capitalize(String str) {
        char[] ch = str.toCharArray();
        if (ch[0] >= 'a' && ch[0] <= 'z') {
            ch[0] = (char) (ch[0] - 32);
        }
        return String.valueOf(ch);
    }

    /**
     * 返回字符串dest在字符串source中出现的次数.要查找的字符串是区分大小写的.
     *
     * @param source 原字符串.
     * @param dest   要搜索的字符串.
     * @return 字符串dest在字符串source中出现的次数.
     */
    public static int containSum(String source, String dest) {
        //这是一个很好的字符串查找算法： 查找dest在source中出现的次数
        return (source.length() - replace(source, dest, "").length()) / dest.length();
    }

    /**
     * 截字符串前面的指定数量个字符.
     * 如果字节长度超过该数量,则后面用省略号代替(因为省略号会占去两个字符，因此实际显示的字符数=toCount-2).
     *
     * @param str     要截取取的字字符串。
     * @param toCount 要截取的字符数（一个汉字计两个字符）,此参数值为负数或0时，将不截取直接返回该str.
     * @return 截取后的新字符串。
     */
    public static String cut(String str, int toCount) {
        if (toCount <= 0) return str;
        StringBuilder reStr = new StringBuilder();
        char[] tempChar = str.toCharArray();
        for (int i = 0; i < tempChar.length; i++) {
            byte[] b = String.valueOf(tempChar[i]).getBytes(Charset.forName("utf-8"));
            toCount -= b.length > 1 ? 2 : 1;
            if (toCount <= 0) {
                if (i == tempChar.length - 1) {
                    reStr.append(tempChar[i]);
                } else {
                    reStr.append("…");
                    break;
                }
            } else {
                reStr.append(tempChar[i]);
            }
        }
        return reStr.toString();
    }


    /**
     * 这是一个字符串注入（或叫做字符串模板替换）方法.
     * 在JDiy日志管理中大量应用了此方法.
     * 首先定义一个带有一个或多个"{}"占位符的字符串模版，然后占位符处的内容会由后面的参数依次注入替换.
     * <br /><strong>下面是一个示例性的代码：</strong><br/>
     * String log = "对不起，要更新的字段出错。字段名:{}, 字段值:{}, 错误原因：{}";<br />
     * String log2 = Txt.inject(log, "name", "ziquee", "无此字段");<br />
     * //执行结果如下：<br />
     * log2="对不起，要更新的字段出错。字段名:name, 字段值:ziquee, 错误原因:无此字段";
     *
     * @param inputString 原始字符串模板.
     * @param injects     注入进去的字符串列表.
     * @return 执行注入后的字符串.
     * @see #injectBy(String, String, String...)
     */
    public static String inject(String inputString, String... injects) {
        return injectBy(inputString, "{}", injects);
    }

    /**
     * 这是一个字符串注入（或叫做字符串模板替换）方法.
     * 与{@link #inject(String, String...)}不同的是，此方法可以自已定义占位符.
     * <br /><strong>下面是一个示例性的代码：</strong><br/>
     * String log = "对不起，要更新的字段出错。字段名:?, 字段值:?, 错误原因：?";<br />
     * String log2 = Txt.injectBy(log, "?", "name", "ziquee", "无此字段");<br />
     * //执行结果如下：<br />
     * log2="对不起，要更新的字段出错。字段名:name, 字段值:ziquee, 错误原因:无此字段";
     *
     * @param inputString  原始字符串模板.
     * @param injectString 字符串占位符.
     * @param injects      注入进去的字符串列表.
     * @return 执行注入后的字符串.
     * @see #inject(String, String...)
     */
    public static String injectBy(String inputString, String injectString, String... injects) {
        if (inputString == null || injects == null || injects.length == 0) return inputString;
        if (injectString == null || "".equals(injectString)) injectString = "{}";
        StringBuilder sb = new StringBuilder();
        int begin = 0, replaced = 0, len = injectString.length(), len1 = injects.length;
        while (true) {
            int sub = inputString.indexOf(injectString, begin);
            if (sub != -1) {
                if (replaced < len1) {
                    sb.append(inputString, begin, sub).append(injects[replaced++]);
                    begin = sub + len;
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        sb.append(inputString.substring(begin));
        return sb.toString();
    }

    /**
     * 判断目标对象（通常是一个字符串）是否为空（空指的是null或者空字符串""， 注意空格，换行符，tab符不是空）.
     *
     * @param str 目标对象
     * @return 若该目标对象为空, 返回true.
     */
    public static boolean isEmpty(Object str) {
        return str == null || "".equals(str);
    }

    /**
     * 判断目标字符序列是否长度大于0.
     *
     * @param str 目标字符序列
     * @return 若长度大于0返回true.
     */
    public static boolean hasLength(CharSequence str) {
        return str != null && str.length() > 0;
    }

    /**
     * 判断目标字符串是否长度大于0.
     *
     * @param str 目标字符串
     * @return 若长度大于0返回true.
     */
    public static boolean hasLength(String str) {
        return hasLength((CharSequence) str);
    }

    /**
     * 判断目标字符序列是否有实际的非空白文本内容.
     * <br/>其中：空(null), 空字符串（""）, 空格,tab符，换行符等等，都属于空白文本.
     *
     * @param str 目标字符序列
     * @return 若有非空白文本返回true.
     */
    public static boolean hasText(CharSequence str) {
        return hasLength(str) && IntStream.range(0, str.length()).anyMatch(i -> !Character.isWhitespace(str.charAt(i)));
    }

    /**
     * 替换字符串(只替换一次).   理论上此方法的性能要高于{@link String#replace(CharSequence, CharSequence)}原生替换,与之不同的是，此方法不使用正则匹配.
     *
     * @param src  原字符串
     * @param from 要替换的字符串
     * @param to   替换成什么
     * @return 替换后的字符串
     */
    public static String replace(String src, String from, String to) {
        int index = src.indexOf(from);
        return index == -1 ? src : src.substring(0, index) + to + src.substring(index + from.length());
    }

    /**
     * 替换全部.   理论上它的性能要高于{@link String#replaceAll(String, String)}原生替换, 与之不同的是，此方法不使用正则匹配.
     *
     * @param src  原字符串
     * @param from 要替换的字符串
     * @param to   替换成什么
     * @return 替换后的字符串
     */
    public static String replaceAll(String src, String from, String to) {
        int index;
        while ((index = src.indexOf(from)) != -1) {
            src = src.substring(0, index) + to + src.substring(index + from.length());
        }
        return src;
    }

    /**
     * 此方法将HTML内容转换为普通文本.  <br/>
     * 此方法将使用正则替换，剃除inputString中的HTML标签，并返回纯文本内容．
     *
     * @param inputString 要转换的HTML代码.
     * @return 转换后的纯文本内容.
     */
    public static String htmlToText(String inputString) {
        String htmlStr = inputString;
        String textStr = "";
        String[] regs = {
                "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>",
                "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>",
                "<[^>]*>",
                "<[^>]*"
        };
        try {
            for (String reg : regs) {
                Pattern scriptPattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE);
                Matcher scriptMatcher = scriptPattern.matcher(htmlStr);
                htmlStr = scriptMatcher.replaceAll("");
            }
            textStr = htmlStr;
        } catch (Exception e) {
            log.error("->Txt.htmlToText(String inputString) ERROR:" + e.getMessage());
        }
        textStr = textStr.replaceAll("&acute;", "\'");
        textStr = textStr.replaceAll("&quot;", "\"");
        textStr = textStr.replaceAll("&lt;", "<");
        textStr = textStr.replaceAll("&gt;", ">");
        textStr = textStr.replaceAll("&nbsp;", " ");
        textStr = textStr.replaceAll("&amp;", "&");
        return textStr;
    }

    /**
     * 将指定的字符串内容进行HTML标签转换过滤. <br />
     * 此方法会将内容中的HTML标签转换成普通字符输出以防止非法的HTML代码被执行; 同时此方法自动识别URL地址、邮件地址并为其添加链接.
     *
     * @param str 要进行HTML标签过滤处理处理的原字符串。
     * @return 转换之后的字符串。
     */
    @SuppressWarnings("unused")
    public static String encodeHTML(String str) {
        String s = str.replaceAll("<", "&lt;").replaceAll(">", "&gt;")
                .replaceAll("\"", "&quot;").replaceAll("'", "&acute; ")
                .replaceAll("  ", " &nbsp;")
                .replaceAll("\\r\\n", "<br/>")
                .replaceAll("\\n", "<br/>")
                .replaceAll("http://([%#=&\\?\\./a-zA-Z0-9]+)", "<a href=\"http://$1\" target=\"_blank\">http://$1</a>")
                .replaceAll("([-_a-z0-9]+?)@([-_\\.a-zA-Z0-9]{5,})", "<a href=\"mailto:$1@$2\" target=\"_blank\">$1@$2</a>");
        return s;
    }
}
